﻿namespace Atomic.Plugins
{
    using System;
    using System.Linq;
    using System.Collections.Generic;
    using System.IO;
    using System.Text;
    using System.Web.Hosting;

    using Atomic.Extensions;
    using Atomic.Plugins.Files;
    using Atomic.Plugins.Exceptions;

    /// <summary>
    /// 默认清单文件管理器。
    /// </summary>
    public class DefaultManifestManager : IManifestManager
    {
        private const string NameSection = "name";
        private const string VersionSection = "version";
        private const string DescriptionSection = "description";
        private const string WebSiteSection = "website";
        private const string AuthorSection = "author";
        private const string AssemblySection = "assembly";

        /// <summary>
        /// 虚拟路径提供程序。
        /// </summary>
        private readonly IVirtualPathProvider _virtualPathProvider = null;

        /// <summary>
        /// 初始化插件获得程序。
        /// </summary>
        /// <param name="virtualPathProvider">虚拟路径提供程序。</param>
        public DefaultManifestManager(IVirtualPathProvider virtualPathProvider)
        {
            this._virtualPathProvider = virtualPathProvider;
        }

        /// <summary>
        /// 获得插件描述信息。
        /// </summary>
        /// <param name="pluginId">插件标识。</param>
        /// <param name="location">插件目录。</param>
        /// <param name="manifestPath">插件配置清单。</param>
        /// <returns>插件描述信息。</returns>
        public PluginDescriptor GetPluginDescriptor(string pluginId, string location, string manifestPath)
        {
            var virtualPath = this._virtualPathProvider.Combine(location, pluginId, "bin");

            var manifestText = this.ReadFile(manifestPath);

            if (string.IsNullOrWhiteSpace(manifestText))
            {
                return new PluginDescriptor
                {
                    Id = pluginId,
                    Name = string.Empty,
                    Version = new Version("1.0.0.1"),
                    Description = string.Empty,
                    WebSite = string.Empty,
                    Author = string.Empty,
                    Location = location,
                    Dependency = this.GetPluginDependencyDescriptor(virtualPath, pluginId + ".dll")
                };
            }

            Dictionary<string, string> manifest = ParseManifest(manifestText);

            string assemblyName = GetValue(manifest, AssemblySection) ?? pluginId + ".dll";

            return new PluginDescriptor
            {
                Id = pluginId,
                Name = GetValue(manifest, NameSection),
                Version = new Version(GetValue(manifest, VersionSection)),
                Description = GetValue(manifest, DescriptionSection),
                WebSite = GetValue(manifest, WebSiteSection),
                Author = GetValue(manifest, AuthorSection),
                Location = location,
                Dependency = this.GetPluginDependencyDescriptor(virtualPath, assemblyName)
            };
        }

        /// <summary>
        /// 关联信息描述。
        /// </summary>
        /// <remarks>
        /// 该方法获取的是与当前插件相对应的程序集信息。
        /// 例：
        /// 插件 Contents，使用的程序集是 Atomic.Content.dll。
        /// 这里返回的就是 Atomic.Content.dll 相关的信息。
        /// </remarks>
        /// <exception cref="Atomic.Plugins.Exceptions.LoadPluginDependencyDescriptorException">插件清单里没有设置“assembly”节点或者设置的“assembly”节点与实际的程序集的名称不一致时，抛出该异常。</exception>
        /// <param name="virtualPath">程序集所在的虚拟目录。</param>
        /// <param name="assemblyName">程序集名称。</param>
        /// <returns>关联信息描述。</returns>
        private DependencyDescriptor GetPluginDependencyDescriptor(string virtualPath, string assemblyName)
        {
            if (string.IsNullOrWhiteSpace(assemblyName))
            {
                throw new LoadPluginDependencyDescriptorException();
            }

            string assemblyPath = this._virtualPathProvider.Combine(virtualPath, assemblyName);

            if (!this._virtualPathProvider.FileExists(assemblyPath))
            {
                throw new LoadPluginDependencyDescriptorException();
            }

            DependencyDescriptor descriptor = new DependencyDescriptor();

            descriptor.Name = Path.GetFileNameWithoutExtension(assemblyName);
            descriptor.VirtualPath = assemblyPath;
            descriptor.References = this.GetReferences(virtualPath, assemblyName);

            return descriptor;
        }

        /// <summary>
        /// 引用项集合。
        /// </summary>
        /// <remarks>
        /// 该方法获取的是当前插件引用的外部程序集的集合的信息。
        /// 例：
        /// 插件 Contents，使用的程序集是 Atomic.Content.dll。
        /// 但是该插件 又引用了两个外部程序集 Test1.dll 与 Test2.dll。
        /// 该方法获得的就是 Test1.dll 与 Test2.dll 的信息的集合。
        /// </remarks>
        /// <param name="virtualPath">程序集所在的虚拟目录。</param>
        /// <param name="assemblyName">程序集名称。</param>
        /// <returns>引用项集合。</returns>
        private IEnumerable<DependencyReferenceDescriptor> GetReferences(string virtualPath, string assemblyName)
        {
            DirectoryInfo folder = new DirectoryInfo(this._virtualPathProvider.MapPath(virtualPath));

            if (!folder.Exists)
            {
                folder.Create();

                return null;
            }

            var files = folder.GetFiles("*.dll");

            if (files == null || files.Length == 0)
            {
                return null;
            }

            return files.SelectMany(file => this.GetReference(virtualPath, file, assemblyName)).ToReadOnlyCollection<DependencyReferenceDescriptor>();
        }

        /// <summary>
        /// 引用项。
        /// </summary>
        /// <param name="virtualPath">程序集所在的虚拟目录。</param>
        /// <param name="file">文件信息。</param>
        /// <param name="assemblyName">程序集名称。</param>
        /// <returns>引用项。</returns>
        private IEnumerable<DependencyReferenceDescriptor> GetReference(string virtualPath, FileInfo file, string assemblyName)
        {
            if (string.Equals(file.Name, assemblyName, StringComparison.OrdinalIgnoreCase))
            {
                return Enumerable.Empty<DependencyReferenceDescriptor>();
            }

            DependencyReferenceDescriptor reference = new DependencyReferenceDescriptor();

            reference.Name = Path.GetFileNameWithoutExtension(file.Name);
            reference.VirtualPath = this._virtualPathProvider.Combine(virtualPath, file.Name);

            return new DependencyReferenceDescriptor[] { reference };
        }

        #region 读取清单文件。

        /// <summary>
        /// 读文件。
        /// </summary>
        /// <param name="virtualPath">虚拟路径。</param>
        /// <returns>文件内容。</returns>
        public string ReadFile(string virtualPath)
        {
            if (!this._virtualPathProvider.FileExists(virtualPath))
            {
                return null;
            }

            using (var stream = _virtualPathProvider.OpenFile(Normalize(virtualPath)))
            {
                var reader = new StreamReader(stream, Encoding.Default);

                return reader.ReadToEnd();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="virtualPath"></param>
        /// <returns></returns>
        private static string Normalize(string virtualPath)
        {
            return HostingEnvironment.VirtualPathProvider.GetFile(virtualPath).VirtualPath;
        }

        /// <summary>
        /// 解析清单。
        /// </summary>
        /// <param name="manifestText">清单路径。</param>
        /// <returns>节点字典集合。</returns>
        private static Dictionary<string, string> ParseManifest(string manifestText)
        {
            var manifest = new Dictionary<string, string>();

            using (StringReader reader = new StringReader(manifestText))
            {
                string line = string.Empty;

                while ((line = reader.ReadLine()) != null)
                {
                    string[] field = line.Split(new[] { ":" }, 2, StringSplitOptions.None);
                    int fieldLength = field.Length;
                    if (fieldLength != 2)
                        continue;

                    //去掉前后空白。
                    for (int i = 0; i < fieldLength; i++)
                    {
                        field[i] = field[i].Trim();
                    }

                    switch (field[0].ToLowerInvariant())
                    {
                        case NameSection:
                            manifest.Add(NameSection, field[1]);
                            break;
                        case VersionSection:
                            manifest.Add(VersionSection, field[1]);
                            break;
                        case DescriptionSection:
                            manifest.Add(DescriptionSection, field[1]);
                            break;
                        case WebSiteSection:
                            manifest.Add(WebSiteSection, field[1]);
                            break;
                        case AuthorSection:
                            manifest.Add(AuthorSection, field[1]);
                            break;
                        case AssemblySection:
                            manifest.Add(AssemblySection, field[1]);
                            break;
                    }
                }
            }

            return manifest;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="fields"></param>
        /// <param name="key"></param>
        /// <returns></returns>
        private static string GetValue(IDictionary<string, string> fields, string key)
        {
            string value;
            return fields.TryGetValue(key, out value) ? value : null;
        }

        #endregion
    }
}